/****************************************************************************

	COPYRIGHT(C) MAXSI SOFTWARE, JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010

    This file is part of Maxsi Engine.

    Maxsi Engine is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Maxsi Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	*/ #include "MaxsiEngineContributors.h" /*

	If you modify this file, please enter your name below and provide contact
	information in MaxsiEngineContributors.h. For more information please see
	MaxsiEngineContributors.h.
	
	Contributors to this file:

	- Jonas 'Sortie' Termansen
	- [your name here]

	MaxsiEngine.dll
	A linkable version of Maxsi Engine.

	MaxsiIOFileSystem.h
	A Simple Flexible Async I/O FileSystem.

****************************************************************************/

#include "MaxsiEngine.h"

#if !defined(ME_IODisabled) || defined(ME_IOEnabled)
namespace MaxsiEngine
{
	ME_IOFileSystem::ME_IOFileSystem()
	{
		IFileName		=	NULL;
		OFileName		=	NULL;
		IFileHND		=	INVALID_HANDLE_VALUE;
		OFileHND		=	INVALID_HANDLE_VALUE;
		IFileWriting	=	false;
		IFileWriting	=	false;
		memset(&IOverlapped,0,sizeof(IOverlapped));
		memset(&OOverlapped,0,sizeof(OOverlapped));
	}

	ME_IOFileSystem::~ME_IOFileSystem()
	{
		if ( IFileName ) { delete[] IFileName; }
		if ( OFileName ) { delete[] OFileName; }
		ME_DONT_COMPILE_RELEASE_BUILD();
		//IFileHND		// Todo: If open, close this!
		//OFileHND		// Todo: If open, close this!
	}

	SIZE_T ME_IOFileSystem::IBufferRequired()
	{
		// Actually, not a lot of space is required for these operations.
		return 16384; // 16 KiB
	}

	SIZE_T ME_IOFileSystem::OBufferRequired()
	{
		// Actually, not a lot of space is required for these operations.
		return 16384; // 16 KiB
	}

	SIZE_T ME_IOFileSystem::IBufferSuggested()
	{
		// For streaming purposes, let's have a good amount of memory available so we won't be a bottleneck.
		return 1024*1024; // 1 MiB
	}

	SIZE_T ME_IOFileSystem::OBufferSuggested()
	{
		// For streaming purposes, let's have a good amount of memory available so we won't be a bottleneck.
		return 1024*1024; // 1 MiB
	}

	int ME_IOFileSystem::IPolicy()
	{
		return ME_IO_CTX_PERSONALBUFFER;
	}

	int ME_IOFileSystem::OPolicy()
	{
		return ME_IO_CTX_PERSONALBUFFER;
	}

	int ME_IOFileSystem::ORangePolicy()
	{
		return ME_IO_RANGES_SUPPORTED;
	}

	int ME_IOFileSystem::IRangePolicy()
	{
		return ME_IO_RANGES_SUPPORTED;
	}

	SIZE_T ME_IOFileSystem::GetIBytesAvailable()
	{
		SIZE_T	Result;

		IContext->Buffer->Lock( ME_BUFFER_LOCK_PARAMS );

		Result	=	IContext->Buffer->GetBufferAvailable();

		IContext->Buffer->Unlock();

		return Result;
	}

	SIZE_T ME_IOFileSystem::GetOBytesAvailable()
	{
		SIZE_T	Result;

		OContext->Buffer->Lock( ME_BUFFER_LOCK_PARAMS );

		Result	=	OContext->Buffer->GetBufferAvailable();

		OContext->Buffer->Unlock();

		return Result;
	}

	bool ME_IOFileSystem::OnThink()
	{
		bool IThinkResult = OnIThink();
		bool OThinkResult = OnOThink();

		return (IThinkResult && OThinkResult);
	}

	bool ME_IOFileSystem::OnIThink()
	{
		if ( !II ) { return false; }
		if ( !IContext ) { return false; }
		if ( !IContext->Buffer ) { return false; }

		if ( IFileWriting ) { return false; }

		ME_Buffer*	Buffer	=	IContext->Buffer;

		Buffer->Lock( ME_BUFFER_LOCK_PARAMS );

		if ( !Buffer->IsInUse() && Buffer->MayRead() == ME_ERROR_SUCCESS && Buffer->MayWrite() == ME_ERROR_SUCCESS )
		{
			if ( Buffer->GetBufferUsed() )
			{
				ME_IORange*	Range		=	GetIRangePos(II.Total);

				if ( Range == NULL || Range->FileName == NULL )
				{
					// Todo: Handle this gracefully.
					IOAssertionFailure(II, 0, "ME_IOFileSystem::OnIThink() requires properly ranged input!");
				}
				else // Range != NULL && Range->FileName != NULL
				{
					if ( OpenWrite(Range->FileName) )
					{						
						//Bytes_t		RangeSize		=	Range->End-Range->Start+1;
						Bytes_t		RangeLeft		=	Range->End-II.Total;
						Bytes_t		RangeOffset		=	Range->Offset+(II.Total-Range->Start);
						char*		Pointer			=	Buffer->GetPointer();
						SIZE_T		WriteLength;

						// Do we have the rest of the range in out buffer?
						if ( RangeLeft > Buffer->GetBufferUsed() )
						{
							// No. There is more. Just write what we do have.
							WriteLength		=	Buffer->GetBufferUsed();
						}
						else
						{
							// Oh yes! Just write it all!
							WriteLength		=	(SIZE_T)RangeLeft;
						}

						// Do we have the entire buffer in one row?			
						if ( Buffer->GetPointerSizeFollowingData() < WriteLength )
						{
							// Nope. Just limit our length.
							WriteLength		=	Buffer->GetPointerSizeFollowingData();
						}

						// Make sure we support files over 4 GB properly!
						IOverlapped.Offset		=	(DWORD)(RangeOffset&0xFFFFFFFF);
						IOverlapped.OffsetHigh	=	(DWORD)(RangeOffset>>32);

						Buffer->SetInUse();

						ScheduleNextThink(ME_NEXT_THINK_WINCALLBACK);

						// Write the file. Our think function will be called when we are done.
						if ( WriteFile((HANDLE)IFileHND,
							Pointer,
							(DWORD)WriteLength,
							NULL,
							&IOverlapped) == FALSE )
						{
							DWORD nLastError = GetLastError();
							if (ERROR_IO_PENDING != nLastError)
							{
								Buffer->ClearInUse();

								// An error happened. Handle the error.
								char*		Error	=	GetWinErrorMessage(GetLastError());
								IOUnavailable(II, GetLastError(), Error);
								delete[]	Error;
							}
						}
					}
					else
					{
						// We failed. Any errors were caught in OpenWrite().
					}
				}
			}
			else
			{
				// No data has arrived yet, just wait.
				ScheduleNextThink(ME_IO_THINK_NO_DATA_YET);
			}
		}
		else
		{
			// We are currenly waiting for a buffer read/write operation to complete.
			ScheduleNextThink(ME_IO_THINK_WINCALLBACK);			
		}

		Buffer->Unlock();

		return true;
	}

	bool ME_IOFileSystem::OpenWrite(char* File)
	{
		if ( !File )
		{
			IOAssertionFailure(II, 0, "ME_IOFileSystem::OpenWrite() had File == NULL!");				
			return false;
		}

		// Don't open the same file twice!
		if ( !File || !IFileName || FileNameCMP(File,IFileName) != 0 )
		{
			if ( IFileHND != INVALID_HANDLE_VALUE ) { CloseHandle(IFileHND); IFileHND = INVALID_HANDLE_VALUE; }

			// Make sure the path exists!
			MKDir(File);

			// TODO: Does CreateFile block, and if it does, does it block enough to make it worthfile to open acsync,
			// and is it even possible to open async in the first place, like it is with network stream connections?
			if (INVALID_HANDLE_VALUE == (IFileHND = CreateFileA(File,GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED,NULL) ))
			{
				char*		Error	=	GetWinErrorMessage(GetLastError());
				IOUnavailable(II, GetLastError(), Error);
				delete[]	Error;

				return false;
			}

			// Clear the possible ERROR_ALREADY_EXISTS
			SetLastError(0);

			if (0 == BindIoCompletionCallback(IFileHND, MaxsiEngine::ME_IOConnection__DoneIO, 0) )
			{
				IOAssertionFailure(II, 0, "ME_IOFileSystem::OpenWrite() couldn't BindIoCompletionCallback()!");
				return false;
			}

			if ( IFileName ) { delete[] IFileName; }
			IFileName		=	BuildString(1,File);
		}
		return true;
	}

	bool	ME_IOFileSystem::IIConnected()
	{
		return BaseClass::IIConnected();
	}
	bool	ME_IOFileSystem::IOConnected()
	{
		return BaseClass::IOConnected();
	}
	bool	ME_IOFileSystem::OIConnected()
	{
		return BaseClass::OIConnected();
	}
	bool	ME_IOFileSystem::OOConnected()
	{
		return BaseClass::OOConnected();
	}
	bool	ME_IOFileSystem::IIDisconnect()
	{
		return BaseClass::IIDisconnect();
	}
	bool	ME_IOFileSystem::OODisconnect()
	{
		return BaseClass::OODisconnect();
	}
	bool	ME_IOFileSystem::IIDisconnected()
	{
		return BaseClass::IIDisconnected();
	}
	bool	ME_IOFileSystem::IODisconnected()
	{
		return BaseClass::IODisconnected();
	}
	bool	ME_IOFileSystem::OIDisconnected()
	{
		return BaseClass::OIDisconnected();
	}
	bool	ME_IOFileSystem::OODisconnected()
	{
		return BaseClass::OODisconnected();
	}	
	bool	ME_IOFileSystem::IIConnect(ME_IOConnectionPointer* Remote)
	{
		return BaseClass::IIConnect(Remote);
	}
	bool	ME_IOFileSystem::OOConnect(ME_IOConnectionPointer* Remote)
	{
		return BaseClass::IIConnect(Remote);
	}
	bool	ME_IOFileSystem::Connected(ME_IOConnectionPointer* Connection)
	{
		return BaseClass::IIConnect(Connection);
	}
	bool	ME_IOFileSystem::Disconnect(ME_IOConnectionPointer* Connection)
	{
		return BaseClass::Disconnect(Connection);
	}
	bool	ME_IOFileSystem::Disconnect()
	{
		return BaseClass::Disconnect();
	}
	bool	ME_IOFileSystem::Disconnected(ME_IOConnectionPointer* Connection)
	{
		return BaseClass::Disconnected(Connection);
	}
	bool	ME_IOFileSystem::Disconnected()
	{
		return BaseClass::Disconnected();
	}
	bool	ME_IOFileSystem::EvaluateConnection()
	{
		ME_TODO("Implement this!");
		return true;
	}

	bool	ME_IOFileSystem::OnOThink()
	{
		return true;
	}

	void	ME_IOFileSystem::DoneIO(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped)
	{
		BaseClass::DoneIO(dwErrorCode,dwNumberOfBytesTransferred,lpOverlapped);
	}

	
}
#endif
